"""Glulx memory classes"""

from array import *
from struct import *
from glulx import GlulxError

class GlulxMemory:
	"A generic Glulx memory class"
	def __init__(self, length):
		self.data = array('B', [0] * length)
		self.curPos = 0

	#	Some useful function wrappers
	def __getitem__(self, key): return self.data[key]
	def __str__(self): return self.data.__str__()

	def read(self, fmt, addr = None, update = True):
		"Read data from the memory according to the format string"
		#	Reading from the current position
		if not addr:
			addr = self.curPos
		size = calcsize(fmt)
		raw = self.data[addr:addr + size].tostring()
		if update:
			self.curPos = addr + size
		return unpack('>' + fmt, raw)

	def write(self, fmt, data, addr = None, update = True):
		"Write data to the memory according to the format string"
		#	Writing to the current position
		if not addr:
			addr = self.curPos
		size = calcsize(fmt)
		raw = pack('>' + fmt, *data)
		self.data[addr:addr + size] = array('B', raw)
		if update:
			self.curPos = addr + size

class GlulxMemoryMap(GlulxMemory):
	"The Glulx memory map"
	def __init__(self, vm):
		self.vm = vm
		GlulxMemory.__init__(self, 0)
		self.protectStart = 0
		self.protectEnd = 0

	def reset(self):
		"Reset the memory map"
		#	Reopen the game file if it's been closed
		if self.vm.game.closed:
			try:
				self.vm.game = open(self.vm.game.name, 'rb')
			except IOError:
				raise GlulxError("Error opening game file")

		#	Read the game file data, and copy it to the memory map. Keep the protected range
		self.vm.game.seek(0, 0)
		oldMem = self.data[self.protectStart:self.protectEnd]
		self.data = array('B', self.vm.game.read(self.vm.headers['EXTSTART']) + chr(0) * (self.vm.headers['ENDMEM'] - self.vm.headers['EXTSTART']))
		self.data[self.protectStart:self.protectEnd] = oldMem
		self.vm.game.close()
		self.curPos = 0

	def protect(self, start, end):
		"Set a range of memory to protect"
		self.protectStart = start
		self.protectEnd = end

class GlulxStack(GlulxMemory):
	"The Glulx stack"
	def __init__(self, vm):
		self.vm = vm
		GlulxMemory.__init__(self, self.vm.headers['STACKSIZE'])

	def pushCallstub(self, destType, destAddr):
		"Push a call stub onto the stack"
		if self.curPos + 16 > self.vm.headers['STACKSIZE']:
			raise GlulxError("Stack overflow in call stub")
		self.write('4L', (destType, destAddr, self.vm.memory.curPos, self.vm.registers['frameptr']))
